import * as fs from 'node:fs/promises';
import { TsFileGenarator } from '../TsFileGenarator.js';
import { getCustomization } from '../configuration/index.js';
import { Memory } from '../memory/index.js';
import { TornaDocDetail } from '../types/type.js';
import { getApiFileName } from './getApiFileName.js';
import { getOutputFilePath } from './getOutputFilePath.js';
import { Helper } from './index.js';

export type OutputFileOpts = {
  outputDirPath: string;
  templateFilePath: string;
  detail: TornaDocDetail;
  visitor: Memory.IUser;
};
export type OutputFileReturn = {
  filePath: string
  tavValues: Record<KnownTavKey, string>
};

export type KnownTavKey =
  'headerValName' |
  'queryValName' |
  'bodyValName' |
  'respValName' |
  'headerTypeName' |
  'queryTypeName' |
  'bodyTypeName' |
  'respTypeName' |
  'howToImport' |
  'queryAllowString' |
  'toInterface' |
  'aliasPath' |
  'lineComment';

export enum Const {
  fileName = '$$_FILE_NAME',
  relativePathWithSuffix = '$$_RELATIVE_PATH_WITH_SUFFIX',
  relativePath = '$$_RELATIVE_PATH',
  aliasPathWithSuffix = '$$_ALIAS_PATH_WITH_SUFFIX',
  aliasPath = '$$_ALIAS_PATH',
}
export const defaultTavValues: Record<KnownTavKey, string> = {
  'headerValName': 'header',
  'queryValName': 'params',
  'bodyValName': 'data',
  'respValName': 'response',
  'headerTypeName': 'HeaderParams',
  'queryTypeName': 'QueryParams',
  'bodyTypeName': 'BodyParams',
  'respTypeName': 'Response',
  'howToImport': `import * as ${Const.fileName} from "${Const.relativePath}"`,
  'queryAllowString': 'true',
  'toInterface': 'true',
  'aliasPath': '',
  'lineComment': '// $$',
};

const supportedTemplateFileExtensionMap = new Map([
  ['.ts', 'ts'],
  ['.dart', 'dart'],
  ['._dart', 'dart'],
] as const);

function getExtensionFromTemplateFilePath(path: string): 'ts' | 'dart' {
  for (const [k, v] of supportedTemplateFileExtensionMap) {
    if (path.endsWith(k)) { return v; }
  }
  throw new Error("不支持此类型的模板文件, 目前支持的后缀: " + Array.from(supportedTemplateFileExtensionMap.keys()).join(', '));
}

export async function outputFile(opts: OutputFileOpts): Promise<OutputFileReturn> {
  const { detail, outputDirPath } = opts;
  const extension = getExtensionFromTemplateFilePath(opts.templateFilePath);

  await fs.mkdir(outputDirPath, { recursive: true });

  const { id, name, url, httpMethod } = detail;

  const fileName = getApiFileName(detail);
  if (!fileName) { throw new Error(`url got empty, id: ${id}, name: ${name}`); }
  if (!httpMethod) { throw new Error(`httpMethod got empty, id:${id}, name: ${name}, url: ${url}`); }


  opts.templateFilePath.endsWith('.ts');
  opts.templateFilePath.endsWith('.dart');


  const filePath = await getOutputFilePath({ detail, outputDirPath, extension });
  const tavValues = { ...defaultTavValues };
  let templateFile = await fs.readFile(opts.templateFilePath).then(v => v.toString());

  // match /*tav* key : value *tav*/
  const kvRegExp = /\/\*tav\*\s*(.*?)\s*:\s*(.*?)\s*\*tav\*\/\s*/;
  const lineRegExp = new RegExp(kvRegExp, 'g');
  templateFile.match(lineRegExp)?.forEach(line => {
    const result = line.match(kvRegExp);
    if (!result) { return; }
    (tavValues as any)[result[1]] = result[2];
  });
  templateFile = templateFile.replace(lineRegExp, '');

  const genarater = new TsFileGenarator();
  const { version } = await Helper.packageInfo();

  const strs = genarater.init({ ...tavValues, genaratorVersion: version }).genarate(detail, opts.visitor);
  const { queryParams, bodyParams, response, headerParams } = genarater;

  const needHeader = !!headerParams?.fieldBodies.length;
  const needQurey = !!queryParams?.fieldBodies.length;
  const needBody = !!bodyParams?.fieldBodies.length;
  const hasResp = !!response?.fieldBodies.length;

  const headerTypeName = needHeader ? headerParams.typeName : 'any';
  const paramsTypeName = needQurey ? queryParams.typeName : 'any';
  const bodyTypeName = needBody ? bodyParams?.typeName : 'any';
  const respTypeName = hasResp ? response.typeName : 'any';

  let fileContent = templateFile;

  const customization = getCustomization(detail);
  if (customization) {
    for (const key in customization) {
      const value = customization[key] ?? customization['*'];
      fileContent = fileContent.replace(new RegExp(key, 'g'), value);
    }
  }

  // $$_FILE_NAME_WITH_SUFFIX
  fileContent = fileContent.replace(/\$\$_FILE_NAME_WITH_SUFFIX/g, fileName)

    // $$_FILE_NAME
    .replace(/\$\$_FILE_NAME/g, fileName.replace(/.ts$/g, ''))

    /* $$_I_HEADER: $$_I_HEADER_TYPE, */
    .replace(/\$\$_I_HEADER([\s]*\:[\s]*)\$\$_I_HEADER_TYPE([\s]*,[\s]*)/g,
      (_, p1, p2) => !needHeader ? '' : (tavValues.headerValName + p1 + headerTypeName + p2))

    /* $$_I_PARAMS: $$_I_PARAMS_TYPE, */
    .replace(/\$\$_I_PARAMS([\s]*\:[\s]*)\$\$_I_PARAMS_TYPE([\s]*,[\s]*)/g,
      (_, p1, p2) => !needQurey ? '' : (tavValues.queryValName + p1 + paramsTypeName + p2))

    /* $$_I_BODY: $$_I_BODY_TYPE, */
    .replace(/\$\$_I_BODY([\s]*\:[\s]*)\$\$_I_BODY_TYPE([\s]*,[\s]*)/g,
      (_, p1, p2) => !needBody ? '' : (tavValues.bodyValName + p1 + bodyTypeName + p2))

    /* (,)$$_I_HEADER: $$_I_HEADER_TYPE */
    .replace(/([,]?[\s]*)\$\$_I_HEADER([\s]*\:[\s]*)\$\$_I_HEADER_TYPE/g,
      (_, p1, p2) => !needHeader ? '' : (p1 + tavValues.headerValName + p2 + headerTypeName))

    /* (,)$$_I_PARAMS: $$_I_PARAMS_TYPE */
    .replace(/([,]?[\s]*)\$\$_I_PARAMS([\s]*\:[\s]*)\$\$_I_PARAMS_TYPE/g,
      (_, p1, p2) => !needQurey ? '' : (p1 + tavValues.queryValName + p2 + paramsTypeName))

    /* (,)$$_I_BODY: $$_I_BODY_TYPE */
    .replace(/([,]?[\s]*)\$\$_I_BODY([\s]*\:[\s]*)\$\$_I_BODY_TYPE/g,
      (_, p1, p2) => !needBody ? '' : (p1 + tavValues.bodyValName + p2 + bodyTypeName))

    /* HEADER_TYPE */
    .replace(/([\s]*)\$\$_I_HEADER_TYPE([,]?)([\s]*)/g,
      (_, p1, p2, p3) => !needHeader ? '' : (p1 + headerTypeName + p2 + p3))

    /* PARAMS_TYPE */
    .replace(/([\s]*)\$\$_I_PARAMS_TYPE([,]?)([\s]*)/g,
      (_, p1, p2, p3) => !needQurey ? '' : (p1 + paramsTypeName + p2 + p3))

    /* BODY_TYPE */
    .replace(/([\s]*)\$\$_I_BODY_TYPE([,]?)([\s]*)/g,
      (_, p1, p2, p3) => !needBody ? '' : (p1 + bodyTypeName + p2 + p3))

    /* HEADER */
    .replace(/\$\$_I_HEADER_OR_UNDEFINED/g, !needHeader ? 'undefined' : tavValues.headerValName)
    .replace(/\$\$_I_HEADER_OR_NULL/g, !needHeader ? 'null' : tavValues.headerValName)
    .replace(/\$\$_I_HEADER_OR_EOBJ/g, !needHeader ? '{}' : tavValues.headerValName)
    .replace(/\$\$_I_HEADER_OR_EARR/g, !needHeader ? '[]' : tavValues.headerValName)
    .replace(/\$\$_I_HEADER_OR_(.*?)/g, (_, replacement) => !needHeader ? replacement : tavValues.headerValName)
    .replace(/([,]?)([\s]*)\$\$_I_HEADER([,|:]?)([\s]*)/g,
      (_, p1, p2, p3, p4) => !needHeader ? '' : p1 + p2 + tavValues.headerValName + p3 + p4)

    /* PARAMS */
    .replace(/\$\$_I_PARAMS_OR_UNDEFINED/g, !needQurey ? 'undefined' : tavValues.queryValName)
    .replace(/\$\$_I_PARAMS_OR_NULL/g, !needQurey ? 'null' : tavValues.queryValName)
    .replace(/\$\$_I_PARAMS_OR_EOBJ/g, !needQurey ? '{}' : tavValues.queryValName)
    .replace(/\$\$_I_PARAMS_OR_EARR/g, !needQurey ? '[]' : tavValues.queryValName)
    .replace(/\$\$_I_PARAMS_OR_(.*?)/g, (_, replacement) => !needQurey ? replacement : tavValues.queryValName)
    .replace(/([,]?)([\s]*)\$\$_I_PARAMS([,|:]?)([\s]*)/g,
      (_, p1, p2, p3, p4) => !needQurey ? '' : p1 + p2 + tavValues.queryValName + p3 + p4)

    /* BODY */
    .replace(/\$\$_I_BODY_OR_UNDEFINED/g, !needBody ? 'undefined' : tavValues.bodyValName)
    .replace(/\$\$_I_BODY_OR_NULL/g, !needBody ? 'null' : tavValues.bodyValName)
    .replace(/\$\$_I_BODY_OR_EOBJ/g, !needBody ? 'null' : tavValues.bodyValName)
    .replace(/\$\$_I_BODY_OR_EARR/g, !needBody ? 'null' : tavValues.bodyValName)
    .replace(/\$\$_I_BODY_OR_(.*?)/g, (_, replacement) => !needBody ? replacement : tavValues.bodyValName)
    .replace(/([,]?)([\s]*)\$\$_I_BODY([,|:]?)([\s]*)/g,
      (_, p1, p2, p3, p4) => !needBody ? '' : p1 + p2 + tavValues.bodyValName + p3 + p4)

    // 下面的还需要在考虑下
    .replace(/\$\$_PARAMS_TYPE_POST_COMMA/g, `${paramsTypeName}, `)
    .replace(/\$\$_PARAMS_TYPE_PREV_COMMA/g, `, ${paramsTypeName}`)
    .replace(/\$\$_PARAMS_TYPE/g, paramsTypeName)
    .replace(/\$\$_PARAMS_POST_COMMA/g, `${tavValues.queryValName}, `)
    .replace(/\$\$_PARAMS_PREV_COMMA/g, `, ${tavValues.queryValName}`)
    .replace(/\$\$_PARAMS/g, tavValues.queryValName)
    .replace(/\$\$_BODY_DECLARE_POST_COMMA/g, `${tavValues.bodyValName}: ${bodyTypeName}, `)
    .replace(/\$\$_BODY_DECLARE_PREV_COMMA/g, `, ${tavValues.bodyValName}: ${bodyTypeName}`)
    .replace(/\$\$_BODY_DECLARE/g, `${tavValues.bodyValName}: ${bodyTypeName}`)
    .replace(/\$\$_BODY_TYPE_POST_COMMA/g, `${bodyTypeName}, `)
    .replace(/\$\$_BODY_TYPE_PREV_COMMA/g, `,${bodyTypeName}`)
    .replace(/\$\$_BODY_TYPE/g, bodyTypeName)
    .replace(/\$\$_BODY_POST_COMMA/g, `${tavValues.bodyValName},`)
    .replace(/\$\$_BODY_PREV_COMMA/g, `, ${tavValues.bodyValName}`)
    .replace(/\$\$_BODY/g, tavValues.bodyValName)
    .replace(/\$\$_I_RESPONSE_DECLARE_POST_COMMA/g, !hasResp ? '' : `${tavValues.respValName}: ${respTypeName}, `)
    .replace(/\$\$_I_RESPONSE_DECLARE_POST_COMMA/g, !hasResp ? '' : `, ${tavValues.respValName}: ${respTypeName}`)
    .replace(/\$\$_I_RESPONSE_DECLARE/g, !hasResp ? '' : `, ${tavValues.respValName}: ${respTypeName}`)
    .replace(/\$\$_I_RESPONSE_TYPE_POST_COMMA/g, !hasResp ? '' : `${respTypeName},`)
    .replace(/\$\$_I_RESPONSE_TYPE_PREV_COMMA/g, !hasResp ? '' : `, ${respTypeName}`)
    .replace(/\$\$_I_RESPONSE_TYPE/g, !hasResp ? '' : respTypeName)
    .replace(/\$\$_I_RESPONSE_POST_COMMA/g, !hasResp ? '' : `${tavValues.respValName},`)
    .replace(/\$\$_I_RESPONSE_PREV_COMMA/g, !hasResp ? '' : `, ${tavValues.respValName}`)
    .replace(/\$\$_I_RESPONSE/g, !hasResp ? '' : tavValues.respValName)
    .replace(/\$\$_RESPONSE_DECLARE_POST_COMMA/g, `${tavValues.respValName}: ${respTypeName}, `)
    .replace(/\$\$_RESPONSE_DECLARE_POST_COMMA/g, `, ${tavValues.respValName}: ${respTypeName}`)
    .replace(/\$\$_RESPONSE_DECLARE/g, `, ${tavValues.respValName}: ${respTypeName}`)
    .replace(/\$\$_RESPONSE_TYPE_POST_COMMA/g, `${respTypeName},`)
    .replace(/\$\$_RESPONSE_TYPE_PREV_COMMA/g, `, ${respTypeName}`)
    .replace(/\$\$_RESPONSE_TYPE/g, respTypeName)
    .replace(/\$\$_RESPONSE_POST_COMMA/g, `${tavValues.respValName},`)
    .replace(/\$\$_RESPONSE_PREV_COMMA/g, `, ${tavValues.respValName}`)
    .replace(/\$\$_RESPONSE/g, tavValues.respValName)
    .replace(/\$\$_URL/g, detail.url)

    .replace(/\$\$_HTTP_METHOD_STR/g, '"' + detail.httpMethod.toUpperCase() + '"') // "GET", "POST", ...
    .replace(/\$\$_UPPER_CASE_HTTP_METHOD_STR/g, '"' + detail.httpMethod.toUpperCase() + '"') // "GET", "POST", ...
    .replace(/\$\$_LOWER_CASE_HTTP_METHOD_STR/g, '"' + detail.httpMethod.toLowerCase() + '"') // "get", "post", ...
    .replace(/\$\$_PASCAL_CASE_HTTP_METHOD_STR/g,
      '"' + detail.httpMethod[0].toUpperCase() + detail.httpMethod.substring(1).toLowerCase() + '"'
    ) // "Get", "post", ...

    .replace(/\$\$_HTTP_METHOD/g, detail.httpMethod.toUpperCase()) // GET, POST, ...
    .replace(/\$\$_UPPER_CASE_HTTP_METHOD/g, detail.httpMethod.toUpperCase()) // GET, POST, ...
    .replace(/\$\$_LOWER_CASE_HTTP_METHOD/g, detail.httpMethod.toLowerCase()) // get, post, ...
    .replace(/\$\$_PASCAL_CASE_HTTP_METHOD/g,
      detail.httpMethod[0].toUpperCase() + detail.httpMethod.substring(1).toLowerCase()
    ) // Get, post, ...

    // CODE-BLOCK
    .replace(...blockReplacing('TS_NS_API_DOC_ZONE', strs.namespaceDocCommentStr))
    .replace(...blockReplacing('FILE_HEADER_ZONE', strs.headerStr))
    .replace(...blockReplacing('TYPE_DECLARATIONS_ZONE', strs.typeDeclarationsStr))
    .replace(...blockReplacing('TYPE_ZONE', strs.headerStr + strs.typeDeclarationsStr));

  await fs.writeFile(filePath, fileContent);
  return { filePath, tavValues };
}
const blockReplacing = (blockName: string, value: string): Parameters<String['replace']> => [
  new RegExp(`([ ]*)\\/\\*tav\\*\\s*(.*?)\\$\\$_${blockName}\\s*\\*tav\\*\\/[\\n|\\r]*`),
  (_, spaces) => spaces + value.replace(/\n/g, '\n' + spaces).replace(/\n[ ]*$/, '\n')
];

